在 Android 造成 OOM 的原因主要有 2 個:
這一篇我們要來談談如何避免用了太多的記憶體。
隨著行動裝置的解析度與相機功能越來越好,拍照的照片檔案也越來越大,可能一個檔案就有 2~3MB,載入至記憶體就很容易發生OOM。
使用 Glide 載入圖片
Android 官方建議大部分的情況,直接使用像 Glide 這樣的圖片載入 Open Source,幫你處理載入圖片、decode、記憶體、Cache 的管理。
加入 dependencies
dependencies {
implementation 'com.github.bumptech.glide:glide:4.14.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.14.0'
}
載入圖片
Glide.with(this).load("imageurl").into(imageView)
注意圖片提供者是否有縮圖版本
Android 所提供的從相機拍照或從相簿取得照片功能,會有一個檔案較小的 thumbnail 的版本。在顯示多張縮圖的圖片時,就適合用 thumbnail 的版本來減少記憶體的使用。
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
//取得thumbnail 小圖
val imageBitmap = data.extras.get("data") as Bitmap
imageView.setImageBitmap(imageBitmap)
}
}
在記憶體中載入縮圖版本
如果我們自已要載入一張圖片至 bitmap,就要在將圖片載入至記憶體時先進行縮圖。假設要載入一張原始大小是 1024*1024 的圖片顯示在 200*200 的ImageView上。如果直接載入1024*1024 大小的圖片卻只顯示在 200*200 的畫面上,顯然是一種浪費。Android 的 BitmapFactory 提供了讓你在載入圖片到記憶體之前先取得圖片大小,方便我們計算實際上只需載入的圖片大小以減少使用記憶體。
步驟如下:
1.取得圖片解析度
下方程式碼,在 mipmap 放了一張圖片 scenery.png。使用BitmapFactory.decodeResource
取得圖片的寬高。在這個階段只是取得圖片資訊,並不會將圖片載入記憶體。
val options = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
//在圖片載入到記憶體前,先取得圖片的寬高
BitmapFactory.decodeResource(resources, R.mipmap.scenery, options)
val imageHeight: Int = options.outHeight
val imageWidth: Int = options.outWidth
val imageType: String = options.outMimeType
2.計算原始圖片縮小倍數
將圖片的真實大小與 UI 上所要顯示的大小來計算圖片需縮小幾倍。
fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
val (height: Int, width: Int) = options.run { outHeight to outWidth }
var inSampleSize = 1
if (height > reqHeight || width > reqWidth) {
val halfHeight: Int = height / 2
val halfWidth: Int = width / 2
while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
inSampleSize *= 2
}
}
return inSampleSize
}
透過 options 設定取得的縮小倍數,再呼叫 BitmapFactory.decodeResource(resources, R.mipmap.scenery, options)
即可依指定的縮小倍數來載入圖片至記憶體。
//記算Size,縮小幾倍
options.inSampleSize = calculateInSampleSize(options, smallImageWidth, smallImageHeight)
options.inJustDecodeBounds = true
binding.imageView.setImageBitmap(
BitmapFactory.decodeResource(resources, R.mipmap.scenery, options)
)
以 1024*1024 的圖片要放到 200*200 的 Imageview,算出來的縮小倍數會是 4,也就是實際上載入的圖片會是1024/4=256。這樣就不會載入過大的圖片而增加 OOM 的機率。
最後,有些情況 OOM 不是馬上就會發生,而是累積一段時間才爆發,所以有時在測試時不容易發現。在後續我們將介紹使用 Profiler 來分析記憶體使用,就可以更容易找出OOM。下一篇將再介紹另一個原因:記憶體無法正常釋放,也就是 Memory leak。